import types, time, random
import MudValidate, Permissions

class Character:
  "Defines a playable character"
  def __init__(self,params=None):
    # Not Saved
    self.d = None
    self.playedLastChecked = 0 # seconds from the epoch that played was last checked
    self.toHit      = 0
    self.toCrit     = 0
    self.toDodge    = 0
    self.toParry    = 0
    self.toBlock    = 0
    self.setable = {
                  'brawn'        : MudValidate.IntegerRange(0,999),
                  'alacrity'     : MudValidate.IntegerRange(0,999),
                  'resilience'   : MudValidate.IntegerRange(0,999),
                  'sagacity'     : MudValidate.IntegerRange(0,999),
                  'health'       : MudValidate.IntegerRange(0,9999),
                  'mana'         : MudValidate.IntegerRange(0,999),
                  'basemaxhealth': MudValidate.IntegerRange(0,9999),
                  'basemaxmana'  : MudValidate.IntegerRange(0,999),
                  'statpoints'   : MudValidate.IntegerRange(0,999)
                  }
    self.timers = {}   # used to keep track of player specific timers
                       # such as auto-attack and cast time
    self.target = None # Combat Target
    self.combat = False # in combat?
    self.maxhealth     = 100 # This is after all effects
    self.maxmana       = 0   # This is after all effects
    self.instance      = None # The current instance they are in
    self.room          = None # The current room they are in
    
    # Saved
    self.name          = ""    
    self.title         = ""
    self.password      = ""
    self.roles         = []
    self.loginTime     = 0  # seconds from the epoch of last login
    self.logoutTime    = 0 # seconds from the epoch of last logout
    self.played        = 0 # Time in seconds played
    self.brawn         = 0 # affects melee attack power
    self.alacrity      = 0 # affects dodge and ranged attack power
    self.resilience    = 0 # affects health and resistance
    self.sagacity      = 0 # affects mana pool and casting abilities
    self.level         = 1
    self.health        = 100 # This is the value that fluctuates during battle
    self.mana          = 0   # This is the value that fluctuates during battle
    self.basemaxhealth = 100 # This is before any effects
    self.basemaxmana   = 0   # This is before any effects
    self.equipped      = {
                      'weapon': None,
                      'helmet': None,
                      'armor' : None,
                      'accessory': None
                      }
    self.inventory  = {}
    self.statpoints = 0
    self.location   = (None,None) # Instance ID, Room ID
    if params:
      self.set(params)
  
  
  def set(self,params):
    if type(params) is types.DictType:
      for p in params.keys():
        try:
          setattr(self,p,params[p])
        except:
          print "Player set: Couldn't set p! (%s)" % p
    else:
      for p in params.__dict__.keys():
        try:
          t = type(getattr(params,p))
          try:
            setattr(self,p,getattr(params,p))
          except:
            print "Player set: Couldn't set %s with %s" (p,getattr(params,p))
        except:
          print "Player set: Couldn't set p! (%s)[%s]" % (p,type(p))
    self.applyBonuses()
  
  
  def toSelf(self,message,prompt=True):
    "Prints a message to a character followed by the prompt"
    self.d.echo(message,prompt)
  
  
  def toWorld(self,message):
    self.d.worldEcho(message)
    
    
  def getPlayed(self):
    "Get the time played for a character"
    pTime = time.time()
    if self.playedLastChecked == 0:
      self.playedLastChecked = self.loginTime
    self.played += pTime - self.playedLastChecked 
    self.playedLastChecked = pTime
    return self.played
  
  
  def secondsToTimeString(self,seconds):
    days = int(seconds / 86400) # one day
    left = seconds - ( days * 86400 )
    hours = int(left / 3600) # one hour    
    mLeft = left - ( hours * 3600 )
    mins  = int(mLeft / 60) # one minute
    return "%d days, %d hours and %d minutes" % (days, hours, mins)
  
  
  def getPlayerInWorld(self,player):
    "Get a player on the server"
    if player.lower() in ["self","me"]:
      return self
    try:
      player = player.capitalize()
    except:
      return None
    if player in self.d.server.connections:
      return self.d.server.connections[player].character
    else:
      return None
  
  
  def gainLevel(self):
    self.level += 1
    self.toSelf("Congratulations! You have gained a level.", False)
    self.statpoints += 5
    self.toSelf("You have been awarded 5 stat points.",False)
    for i in ("brawn","alacrity","resilience","sagacity"):
      bonus = random.randint(1,3)
      setattr(self,i,getattr(self,i)+bonus)
      self.toSelf( "%s has increased by %d." % (i.capitalize(),bonus), False)
    hpinc = random.randint(10,20) + self.resilience*.05
    self.toSelf("Maximum health has increased by %d." % hpinc, False)
    self.basemaxhealth += hpinc
    mpinc = random.randint(1,5) + self.sagacity*.05
    self.toSelf("Maximum mana has increased by %d." % mpinc, False)
    self.basemaxmana += mpinc
    self.applyBonuses()
    self.d.save()
    self.toSelf("Carry on adventurer!")
  
  
  def applyBonuses(self):
    self.maxhealth = int(self.basemaxhealth + self.resilience*.75) # 75% of resilience applies to hp
    self.maxmana   = int(self.basemaxmana   + self.sagacity*.50)    # 50% of sagacity applies to mana
    
    if self.maxhealth > 9999:
      self.maxhealth = 9999
    if self.maxmana > 999:
      self.maxmana = 999
    
    if self.health > self.maxhealth:
      self.health = self.maxhealth
    if self.mana > self.maxmana:
      self.mana = self.maxmana
  
  
  def regen(self,hp,mana):
    if self.health + hp <= self.maxhealth:
      self.health += hp
    else:
      self.health = self.maxhealth
    
    if self.mana + mana <= self.maxmana:
      self.mana += mana
    else:
      self.mana = self.maxmana
  
  
  def goto(self, instance_id, room_id ):
      # do some checking..
      self.move(instance_id, room_id)
  
  
  def move(self, instance_id, room_id):
      """
      Moves a character to a specific room in an instance
      """
      if self.instance is not None:
          self.instance.ch_list.remove(self)
      if self.room is not None:
          self.room.ch_list.remove(self)
      
      # append them to the new room
      new_instance = self.d.server.instances[instance_id]
      new_room     = new_instance.rooms[room_id]
      self.instance = new_instance
      self.room    = new_room
      self.location = (instance_id,room_id)
      if self not in new_instance.ch_list:
        new_instance.ch_list.append(self)
      if self not in new_room.ch_list:
        new_room.ch_list.append(self)
        
      self.interpret("look")  
  
  
  def interpret(self,command):
    "Send something to the interpreter as self"
    self.d.server.interpreter.interpret(self,command)
  
  
  def __getstate__(self):
    self.getPlayed() # Update played time
    return {
            "name":self.name,
            "title":self.title,
            "password":self.password,
            "roles":self.roles,
            "loginTime":self.loginTime,
            "logoutTime":self.logoutTime,
            "played":self.played,
            "brawn":self.brawn,
            "alacrity":self.alacrity,
            "resilience":self.resilience,
            "sagacity":self.sagacity,
            "level":self.level,
            "health":self.health,
            "mana":self.mana,
            "basemaxhealth":self.basemaxhealth,
            "basemaxmana":self.basemaxmana,
            "equipped":self.equipped,
            "inventory":self.inventory,
            "statpoints":self.statpoints,
            "location":self.location
            }


def score_command(ch, args):
  """
  Syntax:
  score
  
  Display statistics about your character.
  """
  s  = "${blue,bold}Score for %s, %s\n" % (ch.name, ch.title)
  s += "${green,bold}----------------------------------------------------------------------\n"
  s += "${reset}"
  s += " Level      : %d\n" % ch.level
  s += " ${red,bold}Health${reset}     : %4d/%4d ${black,bold}(${red,bold}%4d${black,bold})${reset}\n" % (ch.health,ch.maxhealth,ch.basemaxhealth)
  s += " ${blue,bold}Mana${reset}       : %4d/%4d ${black,bold}(${blue,bold}%4d${black,bold})${reset}\n" % (ch.mana,ch.maxmana,ch.basemaxmana)
  s += " Brawn      : %4d\n" % ch.brawn
  s += " Alacrity   : %4d\n" % ch.alacrity
  s += " Resilience : %4d\n" % ch.resilience
  s += " Sagacity   : %4d\n" % ch.sagacity
  s += "${green,bold}----------------------------------------------------------------------\n"
  s += "${reset}"
  s += " Total Time played: %s\n" % (ch.secondsToTimeString(ch.getPlayed()))
  if hasattr(ch,"roles") and type(ch.roles) is not types.NoneType and len(ch.roles) > 0:
    s += "${green,bold}----------------------------------------------------------------------\n"
    s += "${reset}"
    s += " Roles: %s" % ", ".join(ch.roles) # Note that the last line doesn't have a newline
  ch.toSelf(s)

def look_command(ch,args):
    """
    Usage:
    look
    
    Displays the room you are in...
    """
    buf = []
    if ch.room:
        buf.append("${yellow,bold}" + ch.room.name)
        buf.append("${blue,bold}" + ch.room.desc)
        ch.toSelf("\n".join(buf))
    else:
        ch.toSelf("You are floating in the ether!")

def help_command(ch,args):
  """
  Usage:
  help [topic or command]
  
  Displays help for a specific topic or command.
  Lists available commands and topics with no arguments.
  """
  if args[0]:
    topic = args[0]
    try:
      ch.toSelf( "%s%s" %  ("${green,bold}",ch.d.server.interpreter.commandTable[topic].__doc__.strip() ) ) 
    except:
      ch.toSelf( "%s is not a valid help topic." % topic )
  else:
    buf = "${blue,bold}Help Topics:${reset}\n"
    topics = ch.d.server.interpreter.commandTable.keys()
    topics.sort()
    i = 1
    for t in topics:
      if Permissions.isAuthorized(ch,t):
        buf += "%-16s" % t
        if i % 5 == 0:
          buf += "\n"
        i += 1
      
    ch.toSelf( buf )


def title_command(ch,args):
  """
  Usage: 
  title [a new title]
  
  Allows you to set a new title of up to 30 characters.
  With no arguments it will report your title to you.
  """
  title = " ".join(args) or None
  
  if title is not None and len(title) > 30:
    ch.toSelf("Titles cannot be longer than 30 characters")
  elif title is None:
    ch.toSelf("Current title set to '%s'." % ch.title)
  else:
    ch.title = title
    ch.toSelf("Title set to '%s'" % title)
    ch.d.save()


def save_command(ch,args):
  """
  Usage:
  save
  
  Saves your character information.
  """
  
  ch.toSelf("${yellow,bold}Saving...")
  ch.d.save()


def quit_command(ch, args):
  """
  Usage:
  quit
  
  Quits from the game."""
  ch.logoutTime = time.time()
  ch.d.save()
  ch.d.done = True
    
    
def shutdown_command(ch, args):
  """
  Usage:
  shutdown [reason]
  
  Shuts the game down.
  If reason is provided the reason will be broadcast prior to shutdown.
  """
  reason = " ".join(args) or None
  if reason:
    mesg = "%s has issued a shutdown: %s" % (ch.name,reason)
  else:
    mesg = "%s has issued a shutdown." % ch.name
  ch.toWorld("${red,bold}" + mesg)
  print mesg
  ch.d.done = True
  ch.d.server.shutdown()


def reboot_command(ch, args):
  """
  Usage:
  reboot [reason]
  
  Reboots the game.
  If reason is provided, the reason will be broadcast prior to reboot.
  """
  reason = " ".join(args) or None
  if reason:
    mesg = "%s has issued a reboot: %s" % (ch.name,reason)
  else:
    mesg = "%s has issued a reboot." % ch.name
  ch.toWorld("${red,bold}" + mesg)
  print mesg
  ch.d.server.reboot()


def who_command(ch, args):
  """
  Usage:
  who
  
  Displays a list of users currently connected.
  """  
  buf = []
  buf.append( "${blue,bold}Who is connected to %s:${reset}" % (ch.d.server.mudname) )
  for player in ch.d.server.connections.itervalues():
    p = player.character
    buf.append(p.name + ", " + p.title)
  ch.toSelf("\n".join(buf))
  
  
def played_command(ch, args):
  """
  Usage:
  played
  
  Displays hours played.
  """
  played = ch.getPlayed()
  ch.toSelf("${yellow,bold}Time Played: %s" % (ch.secondsToTimeString(ch.getPlayed())) )
  

def advance_command(ch, args):
  """
  Usage:
  advance <target> <level>
  
  Increases the target's level.
  Cannot increase the target's level greater than your own.
  Cannot lower the target's level.
  If you do not specify a level it will try to increase the target's level by one.
  """
  try:
    vict = ch.getPlayerInWorld(args[0])
  except:
    ch.toSelf("Cannot find %s." % args[0].capitalize() )
    return
  
  if vict.level >= ch.level:
    ch.toSelf("You cannot modify %s's level." % vict.name)
  
  try:
    level = int(args[1])
    (ok,v) = MudValidate.IntegerRange(vict.level+1,ch.level)(level)
  except:
    level = vict.level+1
    (ok,v) = MudValidate.IntegerRange(vict.level+1,ch.level)(level)
  
  if ok:
    for i in range(level-vict.level):
      vict.gainLevel()

  elif level < 1:
    ch.toSelf("You cannot set %s's level below %d." % (vict.name,vict.level) )
    return
  else:
    ch.toSelf("You cannot set %s's level above your own." % vict.name)
    return

  ch.toSelf("Raised %s level to %d." % (vict.name,vict.level) )

def mset_command(ch, args):
  """
  Usage:
  mset <target> [list or attr] [value]
  """
  
  try:
    targ = args[0].capitalize()
  except:
    ch.toSelf("You must supply a target.")
    return
      
  try:
    command = args[1]
  except:
    ch.toSelf("You must supply a command. Either list or an avaliable attribute")
    return
  
  # Find the player
  target = ch.getPlayerInWorld(targ)
  if target is None:
    ch.toSelf("Couldn't find %s" % targ)
    return
  
  if command == "list":
    if hasattr(target,'setable'):
      ch.toSelf("Editable attributes:\n  %s" % ("\n  ".join(["${yellow,bold}%-15s${reset} - %s" % (k,v(None)[1]) for k,v in target.setable.items()])))
    else:
      ch.toSelf("Sorry, but %s has no setable attributes." % target.name)
    return
  
  if hasattr(target,command):
    if not hasattr(target,'setable'):
      ch.toSelf("Sorry, but %s has no setable attributes." % target.name)
      return
      
    if command not in target.setable:
      ch.toSelf("Sorry, but %s is not setable on %s." % (command,target.name) )
      return
    
    try:
      val = args[2]
    except:
      ch.toSelf("Set %s to what?" % command)
      return
    
    # Run the Validation
    (ok,v) = target.setable[command](val)
    if not ok:
      ch.toSelf("Cannot set %s to %s on %s: %s" % (command,val,target.name,v))
      return
    
    setattr(target,command,v)
    ch.toSelf("Successfuly set %s to %s on %s." % (command,val,target.name))
    return
    

